/*
 * This file is part of the Try! Engine project.
 *
 * Copyright 2010-2011 Emanuele Bertoldi. All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * You should have received a copy of the modified BSD License along with this
 * program. If not, see <http://www.opensource.org/licenses/bsd-license.php>
 */

#include "scenenode.h"
#include "graphicsmanager.h"

using namespace Try;

SceneNode::SceneNodeLookup SceneNode::s_nodes = SceneNode::SceneNodeLookup();
SceneNode* SceneNode::s_root = 0;

SceneNode::SceneNode(
        const String& name,
        const Transform& t,
        SceneNode* parent
        )
    : m_node(0),
      m_childrenCount(0)
{
    this->setNodeHandle(h3dAddGroupNode(SceneNode::rootNodeHandle(), name.c_str()));
    this->setParent(parent);
    this->setTransform(t);
}

SceneNode::~SceneNode()
{
    std::vector<SceneNodeListener*> listeners = this->listeners();
    int size = listeners.size();

    for (int i=0; i<size; ++i)
        listeners.at(i)->onDeleting(this);

    SceneNode::s_nodes.erase(m_node);

    h3dRemoveNode(m_node);

    m_node = 0;
}

void SceneNode::setName(const String& name)
{
    h3dSetNodeParamStr(m_node, H3DNodeParams::NameStr, name.c_str());
}

void SceneNode::setParent(SceneNode* parent)
{
    if (m_node)
    {
        SceneNode* old_parent = this->parent();

        if (parent != old_parent)
        {
            if (old_parent)
                old_parent->m_childrenCount--;

            if (!parent)
                parent = SceneNode::rootNode();

            if (parent && parent != this)
            {
                h3dSetNodeParent(m_node, parent->nodeHandle());
                parent->m_childrenCount++;
            }

            std::vector<SceneNodeListener*> listeners = this->listeners();
            int size = listeners.size();

            for (int i=0; i<size; ++i)
                listeners.at(i)->onParentChanged(this, parent);
        }
    }
}

void SceneNode::resetTransform()
{
    this->setTransform(Vector3::Zero, Vector3::Zero, Vector3::Identity);
}

void SceneNode::setTransform(const Transform& t)
{
    Vector3 rot = t.rotation.toEulerAngle() * 57.29577951f;

    this->setTransform(t.position, rot, t.scale);
}

void SceneNode::setTransform(
        const Vector3& pos,
        const Vector3& rot,
        const Vector3& scale
)
{
    h3dSetNodeTransform(m_node, pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, scale.x, scale.y, scale.z);

    Transform t(pos, Quaternion(rot), scale);

    std::vector<SceneNodeListener*> listeners = this->listeners();
    int size = listeners.size();
    for (int i=0; i<size; ++i)
        listeners.at(i)->onTransformChanged(this, t);
}

void SceneNode::lookAt(const Vector3& target, const Vector3& up)
{
    // FIXME: is correct?

    Transform t = this->transform();
    Vector3 diff = t.position - target;
    Vector3 rot;

    if (diff.y != 0)
        rot.x = atan2f(diff.y, sqrtf(diff.x*diff.x + diff.z*diff.z));

    if (diff.z != 0 )
        rot.y = atan2(diff.x, diff.z);
    else if (diff.x < 0)
        rot.y = - 3.1415f / 2.0f;
    else
        rot.y = 3.1415f / 2.0f;

    // Convert from radians to degrees.
    rot *= (180 / 3.1415f);

    this->setTransform(t.position, rot, t.scale);
}

void SceneNode::setFlags(int flags, bool recursive)
{
    h3dSetNodeFlags(m_node, flags, recursive);
}

void SceneNode::setFlag(SceneNodeFlags flag, bool value, bool recursive)
{
    int flags = this->flags();

    if (value)
        flags |= flag;
    else
        flags &= ~flag;

    this->setFlags(flags, recursive);
}

String SceneNode::name() const
{
    return h3dGetNodeParamStr(m_node, H3DNodeParams::NameStr);
}

Transform SceneNode::transform() const
{
    Transform t;
    Vector3 rot;

    h3dGetNodeTransform(m_node, &t.position.x, &t.position.y, &t.position.z, &rot.x, &rot.y, &rot.z, &t.scale.x, &t.scale.y, &t.scale.z);

    t.rotation = Quaternion(rot);

    return t;
}

AxisAlignedBoundingBox SceneNode::worldAABB() const
{
    AxisAlignedBoundingBox aabb;

    h3dGetNodeAABB(m_node, &aabb.min.x, &aabb.min.y, &aabb.min.z, &aabb.max.x, &aabb.max.y, &aabb.max.z);

    return aabb;
}

SceneNode* SceneNode::parent() const
{
    NodeHandle parent_node = h3dGetNodeParent(m_node);

    if (SceneNode::isNodeHandleRegistered(parent_node))
        return SceneNode::s_nodes.at(parent_node);

    return 0;
}

SceneNode* SceneNode::child(int index) const
{
    if (index < m_childrenCount)
        return SceneNode::s_nodes.at(h3dGetNodeChild(m_node, index));

    return 0;
}

bool SceneNode::hasChild(SceneNode* node) const
{
    if (node)
        return (node->parent() == this);

    return false;
}

int SceneNode::childrenCount() const
{
    return m_childrenCount;
}

int SceneNode::flags() const
{
    return h3dGetNodeFlags(m_node);
}

bool SceneNode::hasFlags(int flags) const
{
    return (this->flags() & flags);
}

SceneNode* SceneNode::rootNode()
{
    return SceneNode::s_root;
}

SceneNode::SceneNode(const NodeHandle& n)
    : m_node(0),
      m_childrenCount(0)
{
    this->setNodeHandle(n);
}

void SceneNode::setNodeHandle(const NodeHandle& n)
{
    if (n)
    {
        m_node = n;

        SceneNode::s_nodes[n] = this;

        // Initialise the number of current children.
        for (m_childrenCount=0;;m_childrenCount++)
        {
            NodeHandle child = h3dGetNodeChild(m_node, m_childrenCount);
            if (!child)
                break;
        }

        m_childrenCount++;
    }
}

NodeHandle SceneNode::nodeHandle() const
{
    return m_node;
}

NodeHandle SceneNode::rootNodeHandle()
{
    return H3DRootNode;
}

bool SceneNode::isNodeHandleRegistered(const NodeHandle& node)
{
    return (SceneNode::s_nodes.find(node) != SceneNode::s_nodes.end());
}

SceneNode* SceneNode::nodeFromHandle(const NodeHandle& node)
{
    if (node)
    {
        if (SceneNode::isNodeHandleRegistered(node))
            return SceneNode::s_nodes.at(node);

        return SceneNode::parentFromHandle(node);
    }

    return 0;
}

SceneNode* SceneNode::parentFromHandle(const NodeHandle& node)
{
    if (node)
    {
        NodeHandle parent_handle = h3dGetNodeParent(node);

        return SceneNode::nodeFromHandle(parent_handle);
    }

    return 0;
}
